/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "plugins/ecmascript/runtime/ic/profile_type_info.h"
#include "plugins/ecmascript/runtime/ic/ic_handler-inl.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/tagged_array-inl.h"

namespace ark::ecmascript {
void ProfileTypeAccessor::AddElementHandler(JSHandle<JSTaggedValue> dynclass, JSHandle<JSTaggedValue> handler) const
{
    auto profileData = profileTypeInfo_->Get(slotId_);
    if (profileData.IsHole()) {
        return;
    }
    auto index = slotId_;
    if (profileData.IsUndefined()) {
        profileTypeInfo_->Set(thread_, index, dynclass.GetTaggedValue());
        profileTypeInfo_->Set(thread_, index + 1, handler.GetTaggedValue());
        return;
    }
    // clear key ic
    if (profileData.IsString() || profileData.IsSymbol()) {
        profileTypeInfo_->Set(thread_, index, dynclass.GetTaggedValue());
        profileTypeInfo_->Set(thread_, index + 1, handler.GetTaggedValue());
        return;
    }
    AddHandlerWithoutKey(dynclass, handler);
}

void ProfileTypeAccessor::AddHandlerWithoutKey(JSHandle<JSTaggedValue> dynclass, JSHandle<JSTaggedValue> handler) const
{
    if (IsNamedGlobalIC(GetKind())) {
        profileTypeInfo_->Set(thread_, slotId_, handler.GetTaggedValue());
        return;
    }
    auto index = slotId_;
    auto profileData = profileTypeInfo_->Get(index);
    if (profileData.IsHole()) {
        return;
    }
    while (index < slotId_ + 4U && !profileTypeInfo_->Get(index).IsUndefined()) {
        index += 2U;
    }
    if (index == slotId_ + 4U) {
        profileTypeInfo_->Set(thread_, slotId_, JSTaggedValue::Hole());
        profileTypeInfo_->Set(thread_, slotId_ + 1, JSTaggedValue::Hole());
        profileTypeInfo_->Set(thread_, slotId_ + 2, JSTaggedValue::Hole());
        profileTypeInfo_->Set(thread_, slotId_ + 3, JSTaggedValue::Hole());
        return;
    }
    profileTypeInfo_->Set(thread_, index, dynclass.GetTaggedValue());
    profileTypeInfo_->Set(thread_, index + 1, handler.GetTaggedValue());
}

void ProfileTypeAccessor::AddHandlerWithKey(JSHandle<JSTaggedValue> key, JSHandle<JSTaggedValue> dynclass,
                                            JSHandle<JSTaggedValue> handler) const
{
    if (IsValueGlobalIC(GetKind())) {
        AddGlobalHandlerKey(key, handler);
        return;
    }
    auto profileData = profileTypeInfo_->Get(slotId_);
    if (profileData.IsUndefined() || profileTypeInfo_->Get(slotId_ + 1).IsUndefined()) {
        profileTypeInfo_->Set(thread_, slotId_, key.GetTaggedValue());
        profileTypeInfo_->Set(thread_, slotId_ + 1U, dynclass.GetTaggedValue());
        profileTypeInfo_->Set(thread_, slotId_ + 2U, handler.GetTaggedValue());
        return;
    }
    // for element ic, profileData may dynclass or taggedarray
    if (key.GetTaggedValue() != profileData) {
        profileTypeInfo_->Set(thread_, slotId_, JSTaggedValue::Hole());
        profileTypeInfo_->Set(thread_, slotId_ + 1, JSTaggedValue::Hole());
        profileTypeInfo_->Set(thread_, slotId_ + 2, JSTaggedValue::Hole());
        return;
    }
}

void ProfileTypeAccessor::AddGlobalHandlerKey(JSHandle<JSTaggedValue> key, JSHandle<JSTaggedValue> handler) const
{
    uint32_t index = slotId_;
    JSTaggedValue indexVal = profileTypeInfo_->Get(index);
    if (indexVal.IsUndefined()) {
        profileTypeInfo_->Set(thread_, index, key.GetTaggedValue());
        profileTypeInfo_->Set(thread_, index + 1, handler.GetTaggedValue());
        return;
    }
    index += 2U;
    if (indexVal.IsUndefined()) {
        profileTypeInfo_->Set(thread_, index, key.GetTaggedValue());
        profileTypeInfo_->Set(thread_, index + 1, handler.GetTaggedValue());
        return;
    }
    profileTypeInfo_->Set(thread_, slotId_, JSTaggedValue::Hole());
}

void ProfileTypeAccessor::SetAsMega() const
{
    profileTypeInfo_->Set(thread_, slotId_, JSTaggedValue::Hole());
    if (!IsNamedGlobalIC(GetKind())) {
        profileTypeInfo_->Set(thread_, slotId_ + 1, JSTaggedValue::Hole());
    }
}

std::string ICKindToString(ICKind kind)
{
    switch (kind) {
        case ICKind::NAMED_LOAD_IC:
            return "NamedLoadIC";
        case ICKind::NAMED_STORE_IC:
            return "NamedStoreIC";
        case ICKind::LOAD_IC:
            return "LoadIC";
        case ICKind::STORE_IC:
            return "StoreIC";
        case ICKind::NAMED_GLOBAL_LOAD_IC:
            return "NamedGlobalLoadIC";
        case ICKind::NAMED_GLOBAL_STORE_IC:
            return "NamedGlobalStoreIC";
        case ICKind::TRY_NAMED_GLOBAL_LOAD_IC:
            return "TryNamedGlobalLoadIC";
        case ICKind::TRY_NAMED_GLOBAL_STORE_IC:
            return "TryNamedGlobalStoreIC";
        case ICKind::GLOBAL_LOAD_IC:
            return "GlobalLoadIC";
        case ICKind::GLOBAL_STORE_IC:
            return "GlobalStoreIC";
        default:
            UNREACHABLE();
    }
    UNREACHABLE();
}

std::string ProfileTypeAccessor::ICStateToString(ProfileTypeAccessor::ICState state)
{
    switch (state) {
        case ICState::UNINIT:
            return "uninit";
        case ICState::MONO:
            return "mono";
        case ICState::POLY:
            return "poly";
        case ICState::MEGA:
            return "mega";
        default:
            UNREACHABLE();
    }
    UNREACHABLE();
}

ProfileTypeAccessor::ICState ProfileTypeAccessor::GetICState() const
{
    auto profileData = profileTypeInfo_->Get(slotId_);
    if (profileData.IsUndefined()) {
        return ICState::UNINIT;
    }

    if (profileData.IsHole()) {
        return ICState::MEGA;
    }

    switch (kind_) {
        case ICKind::NAMED_LOAD_IC:
        case ICKind::NAMED_STORE_IC:
            if (profileData.IsWeak()) {
                return ICState::MONO;
            }
            ASSERT(profileData.IsTaggedArray());
            return ICState::POLY;
        case ICKind::LOAD_IC:
        case ICKind::STORE_IC: {
            if (profileData.IsWeak()) {
                return ICState::MONO;
            }
            if (profileData.IsTaggedArray()) {
                TaggedArray *array = TaggedArray::Cast(profileData.GetHeapObject());
                return array->GetLength() == MONO_CASE_NUM ? ICState::MONO : ICState::POLY;  // 2 : test case
            }
            profileData = profileTypeInfo_->Get(slotId_ + 1);
            TaggedArray *array = TaggedArray::Cast(profileData.GetHeapObject());
            return array->GetLength() == MONO_CASE_NUM ? ICState::MONO : ICState::POLY;  // 2 : test case
        }
        case ICKind::NAMED_GLOBAL_LOAD_IC:
        case ICKind::NAMED_GLOBAL_STORE_IC:
            ASSERT(profileData.IsPropertyBox());
            return ICState::MONO;
        case ICKind::GLOBAL_LOAD_IC:
        case ICKind::GLOBAL_STORE_IC: {
            ASSERT(profileData.IsTaggedArray());
            TaggedArray *array = TaggedArray::Cast(profileData.GetHeapObject());
            return array->GetLength() == MONO_CASE_NUM ? ICState::MONO : ICState::POLY;  // 2 : test case
        }
        default:
            UNREACHABLE();
    }
    return ICState::UNINIT;
}
}  // namespace ark::ecmascript
